﻿@inherits TestComponentBase

<Fixture Setup="() => Services.AddMockJsRuntime()"
         Test="EmptyTodoList"
         Tests="new Action[]{ SettingLabel, TaskListRendersItemsUsingItemTemplate }">
    <ComponentUnderTest>
        <TodoList>
            <ItemsTemplate>
                <TodoItem Todo=@context />
            </ItemsTemplate>
        </TodoList>
    </ComponentUnderTest>
    <Fragment Id="EmptyTodoListRender">
        <form>
            <div class="input-group">
                <input value="" type="text" class="form-control"
                       placeholder="Task description" aria-label="Task description" />
                <div class="input-group-append">
                    <button class="btn btn-secondary" type="submit">Add task</button>
                </div>
            </div>
        </form>
        <ol class="list-group"></ol>
    </Fragment>
    <Fragment Id="TodoItemRender">
        <TodoItem Todo="@TestItems[0]" />
    </Fragment>
</Fixture>
@code {
    Todo[] TestItems { get; } = new[] { new Todo { Id = 42 } };

    void EmptyTodoList()
    {
        // Act - get the CUT
        var cut = GetComponentUnderTest<TodoList>();

        // Assert - get the expected initial rendered HTML from the fragment 
        // and use it to verify the initial rendered HTML
        var expectedInitialRender = GetFragment("EmptyTodoListRender");
        cut.MarkupMatches(expectedInitialRender);
    }

    void SettingLabel()
    {
        // Arrange - get the CUT
        var cut = GetComponentUnderTest<TodoList>();

        // Act - update label 
        cut.SetParametersAndRender((nameof(TodoList.Label), "LABEL"));

        // Assert - verifiy that the placeholder and aria-label has changed
        cut.GetChangesSinceFirstRender().ShouldAllBe(
            diff => diff.ShouldBeAttributeChange("placeholder", "LABEL"),
            diff => diff.ShouldBeAttributeChange("aria-label", "LABEL")
        );
    }

    void TaskListRendersItemsUsingItemTemplate()
    {
        // Arrange - get the cut and take a snapshot of the current render tree output
        var cut = GetComponentUnderTest<TodoList>();
        cut.SaveSnapshot();

        // Act - assign test todo items to the CUT
        cut.SetParametersAndRender((nameof(TodoList.Items), TestItems));

        // Assert - get the diffs since the snapshot and compare to the expected.
        var diffs = cut.GetChangesSinceSnapshot();
        var expected = GetFragment("TodoItemRender");
        diffs.ShouldHaveSingleChange().ShouldBeAddition(expected);
    }
}

<Fixture Setup="Setup"
         Tests="new Action[]{ OnFirstRenderInputFieldGetsFocus,
                AfterFirstRenderInputFieldDoesntGetFocusAfterRerenders,
                WhenAddTaskFormIsSubmittedWithNoTextOnAddingTodoIsNotCalled }">
    <ComponentUnderTest>
        <TodoList OnAddingTodo="OnAddingTodoHandler">
            <ItemsTemplate>
                <TodoItem Todo=@context />
            </ItemsTemplate>
        </TodoList>
    </ComponentUnderTest>
</Fixture>
@code {
    MockJsRuntimeInvokeHandler jsRtMock = default!;
    Todo? createdTodo;

    void OnAddingTodoHandler(Todo todo) => createdTodo = todo;

    void Setup()
    {
        jsRtMock = Services.AddMockJsRuntime();
    }

    void OnFirstRenderInputFieldGetsFocus()
    {
        // Act
        var cut = GetComponentUnderTest<TodoList>();

        // Assert that there is a call to document.body.focus.call with a single argument,
        // a reference to the input element.
        jsRtMock.VerifyInvoke("document.body.focus.call")
            .Arguments.Single().ShouldBeElementReferenceTo(cut.Find("input"));
    }

    void AfterFirstRenderInputFieldDoesntGetFocusAfterRerenders()
    {
        // Arrange
        var cut = GetComponentUnderTest<TodoList>();

        // Act
        cut.Render(); // second render
        cut.Render(); // thrid render
        cut.Render(); // ...
        cut.Render();

        // Assert that focus logic only runs on first render (only called 1 time).
        jsRtMock.VerifyInvoke("document.body.focus.call", calledTimes: 1);
    }

    void WhenAddTaskFormIsSubmittedWithNoTextOnAddingTodoIsNotCalled()
    {
        // Arrange
        var cut = GetComponentUnderTest<TodoList>();

        // Act - submit the empty form
        cut.Find("form").Submit();

        // Assert - verify that no task was created
        Assert.Null(createdTodo);
    }

    void WhenAddTaskFormIsSubmittedWithTextOnAddingTodoIsCalled()
    {
        // Arrange - ensure createdTask is null
        createdTodo = null;
        var cut = GetComponentUnderTest<TodoList>();
        var taskValue = "HELLO WORLD TASK";

        // Act - find input field and change its value, then submit the form
        cut.Find("input").Change(taskValue);
        cut.Find("form").Submit();

        // Assert that a new task has been passed to the EventCallback listener and that the 
        // new task has the expected value
        Assert.NotNull(createdTodo);
        Assert.Equal(taskValue, createdTodo?.Text);
    }
}
